#include "../pch.h"
#include "CharUnicodeInfo.h"

namespace BFX
{

//////////////////////////////////////////////////////////////////////////////

bool CharUnicodeInfo::s_bInitialized = false;
SpinLock CharUnicodeInfo::s_initLock;	// we use spinlock here for low level initialization.

UINT16*	CharUnicodeInfo::s_pCategoryLevel1Index;
BYTE*	CharUnicodeInfo::s_pCategoriesValue;
UINT16*	CharUnicodeInfo::s_pNumericLevel1Index;
BYTE*	CharUnicodeInfo::s_pNumericValues;
CharUnicodeInfo::DigitValues*	CharUnicodeInfo::s_pDigitValues;

BYTE*	CharUnicodeInfo::s_pUpperCaseTable;
BYTE*	CharUnicodeInfo::s_pLowerCaseTable;
BYTE*	CharUnicodeInfo::s_pTitleCasingTable;
CharUnicodeInfo::CasingPlaneOffset*	CharUnicodeInfo::s_pPlaneOffset;

void CharUnicodeInfo::StaticInitialize()
{
	if (!s_bInitialized)
	{
		SpinLockHolder holder(&s_initLock);

		if (!s_bInitialized)
		{
			UnicodeDataHeader* pudh = (UnicodeDataHeader*) s_pDataTable;
			s_pCategoryLevel1Index = (UINT16*)(s_pDataTable + pudh->OffsetToCategoriesIndex);
			s_pCategoriesValue = (BYTE*)(s_pDataTable + pudh->OffsetToCategoriesValue);
			s_pNumericLevel1Index = (UINT16*)(s_pDataTable + pudh->OffsetToNumbericIndex);
			s_pNumericValues = (BYTE*)(s_pDataTable + pudh->OffsetToNumbericValue);
			s_pDigitValues = (DigitValues*)(s_pDataTable + pudh->OffsetToDigitValue);

			CasingDataHeader* pcdh = (CasingDataHeader*) s_pCasingTable;
			s_pUpperCaseTable = (BYTE*)s_pCasingTable + pcdh->OffsetToUpperCasingTable;
			s_pLowerCaseTable = (BYTE*)s_pCasingTable + pcdh->OffsetToLowerCasingTable;
			s_pTitleCasingTable = (BYTE*)s_pCasingTable + pcdh->OffsetToTitleCaseTable;
			s_pPlaneOffset = &(pcdh->PlaneOffset[0]);
			// NOTE: language exception unimplemented.

			s_bInitialized = true;
		}
	}
}

// Returns the Unicode Category property for the character ch.
// Note that this API will return values for D800-DF00 surrogate halves.
CharUnicodeInfo::UnicodeCategory CharUnicodeInfo::GetUnicodeCategory(UCHAR ch, int nOffset)
{
	// BFX_REQUIRE((ch >= 0 && ch <= 0x10ffff) && "ch is not in valid Unicode range.");

	StaticInitialize();

	// Get the level 2 item from the highest 12 bit (8 - 19) of ch.
	UINT16 index = s_pCategoryLevel1Index[ch >> 8];
	// Get the level 2 WORD offset from the 4 - 7 bit of ch.  This provides the base offset of the level 3 table.
	// Note that & has the lower precedence than addition, so don't forget the parathesis.
	index = s_pCategoryLevel1Index[index + ((ch >> 4) & 0x000f)];
	BYTE* pBytes = (BYTE*) (s_pCategoryLevel1Index + index);
	// Get the result from the 0 -3 bit of ch.
	BYTE iValueIndex = pBytes[ch & 0x000f];
	BYTE uc = s_pCategoriesValue[(iValueIndex * 2) + nOffset];

	return (UnicodeCategory)uc;
}

// Determines if the given character is a white space character.
bool CharUnicodeInfo::IsWhiteSpace(UCHAR ch)
{
	// latin1 char.
	if (ch <= 0x00ff/* '\x00ff' */)
	{
		if (((ch != ' ') && ((ch < '\t') || (ch > '\r'))) && ((ch != 0x00a0/*'\x00a0'*/) && (ch != 0x0085/*'\x0085'*/)))
		{
			return false;
		}
		return true;
	}
	// unicode char.
	switch (GetUnicodeCategory(ch))
	{
	case SpaceSeparator:
	case LineSeparator:
	case ParagraphSeparator:
		return true;
	default:
		return false;
	}
}

UCHAR CharUnicodeInfo::ChangeCaseChar(bool bIsToUpper, UCHAR ch)
{
	StaticInitialize();

	UINT16* pIndexTable = (UINT16*)(bIsToUpper ? s_pUpperCaseTable : s_pLowerCaseTable);
	UINT16 value = pIndexTable[ch >> 8];
	value = pIndexTable[value + ((ch >> 4) & 0xf)];
	value = pIndexTable[value + (ch & 0xf)];

	if (value == W_HIGH_SURROGATE_START)
	{
	    // The high surrogate characters are marked as D800 (HIGH_SURROGATE_START).  In this case,
		// we should just return the high surrogate character.
		return (ch);
	}
	return (ch + (int)((short)value));
}

static UCHAR InternalChangeCaseChar(UCHAR* pIndexTable, UCHAR wch)
{
	WORD value = pIndexTable[wch >> 8];
	value = pIndexTable[value + ((wch >> 4) & 0xf)];
	value = pIndexTable[value + (wch & 0xf)];
	return (wch + (int)((short)value));
}

void CharUnicodeInfo::ChangeCaseString(bool bIsToUpper, const UCHAR* pchSource, UCHAR* pchTarget, int nStrLen)
{
	StaticInitialize();

	UINT16* pIndexTable = (UINT16*)(bIsToUpper ? s_pUpperCaseTable : s_pLowerCaseTable);

	for (int i = 0; i < nStrLen; i++)
	{
        UCHAR wch = pchSource[i];
        UINT16 value = pIndexTable[wch >> 8];
        value = pIndexTable[value + ((wch >> 4) & 0xf)];
        value = pIndexTable[value + (wch & 0xf)];
        if (value != W_HIGH_SURROGATE_START)
		{
            pchTarget[i] = wch + value;
        }
		else
		{
            // This is a high surrogate
            if (i+1 < nStrLen)
			{
                UCHAR wch2 = pchSource[i+1] - W_LOW_SURROGATE_START;
                if (wch2 <= 0x03ff)
				{
                    // This is a surrogate pair.
                    int plane = ((wch - W_HIGH_SURROGATE_START) >> 6);
                    UINT32 offset = (bIsToUpper ? s_pPlaneOffset[plane].OffsetToUppercasing: s_pPlaneOffset[plane].OffsetToLowercasing);
                    if (offset != 0)
					{
                        UINT16* pSurrogateIndexTable = (UINT16*)(s_pCasingTable + offset );
                        UCHAR result = InternalChangeCaseChar(pSurrogateIndexTable, (wch << 10) + wch2);
                        // 0xfc00 = 0x1111 1111 1100 0000
                        // We masked off the lowest 6 bits so that the plane of the wch1 is preserved.
                        // Here 
                        // Then we add the highest 6 bits of the result into the high surrogate.
                        // 
                        pchTarget[i] = W_HIGH_SURROGATE_START  + (result >> 10); 
                        // 0x03ff = 0x0000 0011 1111 1111
                        // We get the lowest 10 bits of the result and make it a low surrogate.
                        pchTarget[++i] = W_LOW_SURROGATE_START + (result & 0x3ff);;      
                    }
					else
					{
                        pchTarget[i] = wch;
                        i++;    // Advance to next character.
                        pchTarget[i] = pchSource[i];
                    }
                } else
				{
                    pchTarget[i] = wch;
                }
            }
			else
			{
                pchTarget[i] = wch;
            }
		}
	}
}

}	//	namespace BFX
